﻿using JetBrains.Annotations;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Localization;
using OpenIddict.Abstractions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.PermissionManagement;
using Volo.Abp.Uow;

namespace GMall.AdministrationService.DataSeed
{
    public class OpenIddictDataSeeder : ITransientDependency, IOpenIddictDataSeeder
    {
        private readonly IConfiguration _configuration;
        private readonly IOpenIddictApplicationManager _applicationManager;
        private readonly IOpenIddictScopeManager _scopeManager;
        private readonly IPermissionDataSeeder _permissionDataSeeder;
        private readonly IStringLocalizer<OpenIddictResponse> L;
        private List<string> Scopes;
        public OpenIddictDataSeeder(
            IConfiguration configuration,
            IOpenIddictApplicationManager applicationManager,
            IOpenIddictScopeManager scopeManager,
            IPermissionDataSeeder permissionDataSeeder,
            IStringLocalizer<OpenIddictResponse> l)
        {
            _configuration = configuration;
            _applicationManager = applicationManager;
            _scopeManager = scopeManager;
            _permissionDataSeeder = permissionDataSeeder;
            L = l;
            Scopes = new List<string>();
        }

        [UnitOfWork]
        public virtual async Task SeedAsync()
        {
            var applications = _configuration.GetSection("OpenIddict:Applications").GetChildren().Select(c => c.Key.Split("_")[0]);
            if (applications.Any())
            {
                Scopes.AddRange(applications);
            }
            Scopes.Add("AuthServer");
            //Scopes = new List<string>()
            //{
            //    "AdministrationService",
            //    "ProductService",
            //    "AuthServer",
            //    "AdminGateway",
            //    "Admin"
            //};
            await CreateScopesAsync();
            await CreateApplicationsAsync();
        }
        private async Task CreateScopesAsync()
        {
            if (Scopes?.Any() ?? false)
            {
                foreach (var scope in Scopes)
                {
                    if (await _scopeManager.FindByNameAsync(scope) == null)
                    {
                        await _scopeManager.CreateAsync(new OpenIddictScopeDescriptor
                        {
                            Name = scope,
                            DisplayName = $"{scope} API",
                            Resources =
                    {
                        scope
                    }
                        });
                    }
                }
            }
        }

        private async Task CreateApplicationsAsync()
        {
            var configurationSection = _configuration.GetSection("OpenIddict:Applications");

            var commonScopes = new List<string>
            {
                OpenIddictConstants.Permissions.Scopes.Profile,
                OpenIddictConstants.Permissions.Scopes.Email,
                OpenIddictConstants.Permissions.Scopes.Phone,
                OpenIddictConstants.Permissions.Scopes.Address,
                OpenIddictConstants.Permissions.Scopes.Roles,
            };
            if (Scopes?.Any() ?? false)
            {
                commonScopes.AddRange(Scopes);
            }

            var commonClaims = new List<string>
            {
                OpenIddictConstants.Claims.Subject,
                OpenIddictConstants.Claims.Username,
                OpenIddictConstants.Claims.Name,
                OpenIddictConstants.Claims.Nickname,
                OpenIddictConstants.Claims.Role,
                OpenIddictConstants.Claims.Email,
                OpenIddictConstants.Claims.PhoneNumber,
                OpenIddictConstants.Claims.Profile
            };


            #region Swagger Client
            var configurationSection_Swagger = configurationSection?.GetChildren()?.Where(o => o.Key.Contains("_Swagger"))?.ToList();
            if (configurationSection_Swagger?.Any() ?? false)
            {
                foreach (var child in configurationSection_Swagger)
                {
                    var swaggerClientId = child["ClientId"];
                    if (!swaggerClientId.IsNullOrWhiteSpace())
                    {
                        var swaggerRootUrl = child["RootUrl"].TrimEnd('/');
                        var swaggerClientSecret = child["ClientSecret"] ?? "1q2w3e*";
                        await CreateApplicationAsync(
                            name: swaggerClientId,
                            type: OpenIddictConstants.ClientTypes.Confidential,
                            consentType: OpenIddictConstants.ConsentTypes.Implicit,
                            displayName: $"{swaggerClientId} Application",
                            secret: swaggerClientSecret,
                            grantTypes: new List<string>
                            {
                            OpenIddictConstants.GrantTypes.AuthorizationCode
                            },
                            scopes: commonScopes,
                            redirectUri: swaggerRootUrl + "/swagger/oauth2-redirect.html"
                        );
                    }
                }
            }
            #endregion

            #region Web Client
            var configurationSection_Web = configurationSection?.GetChildren()?.Where(o => o.Key.Contains("_Web"))?.ToList();
            if (configurationSection_Web?.Any() ?? false)
            {
                foreach (var child in configurationSection_Web)
                {
                    var webClientId = child["ClientId"];
                    if (!webClientId.IsNullOrWhiteSpace())
                    {
                        var webClientRootUrl = child["RootUrl"].TrimEnd('/');
                        var webClientSecret = child["ClientSecret"] ?? "1q2w3e*";
                        await CreateApplicationAsync(
                            name: webClientId,
                            type: OpenIddictConstants.ClientTypes.Confidential,
                            consentType: OpenIddictConstants.ConsentTypes.Implicit,
                            displayName: $"{webClientId} Application",
                            secret: webClientSecret,
                            grantTypes: new List<string> //Hybrid flow
                            {
                            OpenIddictConstants.GrantTypes.AuthorizationCode,
                            OpenIddictConstants.GrantTypes.Implicit,
                            OpenIddictConstants.GrantTypes.Password,
                            OpenIddictConstants.GrantTypes.ClientCredentials,
                            OpenIddictConstants.GrantTypes.RefreshToken
                            },
                            scopes: commonScopes,
                            redirectUri: webClientRootUrl + "/signin-oidc",
                            postLogoutRedirectUri: webClientRootUrl + "/signout-callback-oidc"
                        );
                    }
                }
            }
            #endregion

            #region

            ////Console Test / Angular Client
            //var consoleAndAngularClientId = configurationSection["GMall_App:ClientId"];
            //if (!consoleAndAngularClientId.IsNullOrWhiteSpace())
            //{
            //    var consoleAndAngularClientRootUrl = configurationSection["GMall_App:RootUrl"]?.TrimEnd('/');
            //    await CreateApplicationAsync(
            //        name: consoleAndAngularClientId,
            //        type: OpenIddictConstants.ClientTypes.Public,
            //        consentType: OpenIddictConstants.ConsentTypes.Implicit,
            //        displayName: "Console Test / Angular Application",
            //        secret: null,
            //        grantTypes: new List<string>
            //        {
            //            OpenIddictConstants.GrantTypes.AuthorizationCode,
            //            OpenIddictConstants.GrantTypes.Password,
            //            OpenIddictConstants.GrantTypes.ClientCredentials,
            //            OpenIddictConstants.GrantTypes.RefreshToken
            //        },
            //        scopes: commonScopes,
            //        redirectUri: consoleAndAngularClientRootUrl,
            //        postLogoutRedirectUri: consoleAndAngularClientRootUrl
            //    );
            //}

            //// Blazor Client
            //var blazorClientId = configurationSection["GMall_Blazor:ClientId"];
            //if (!blazorClientId.IsNullOrWhiteSpace())
            //{
            //    var blazorRootUrl = configurationSection["GMall_Blazor:RootUrl"].TrimEnd('/');

            //    await CreateApplicationAsync(
            //        name: blazorClientId,
            //        type: OpenIddictConstants.ClientTypes.Public,
            //        consentType: OpenIddictConstants.ConsentTypes.Implicit,
            //        displayName: "Blazor Application",
            //        secret: null,
            //        grantTypes: new List<string>
            //        {
            //            OpenIddictConstants.GrantTypes.AuthorizationCode,
            //        },
            //        scopes: commonScopes,
            //        redirectUri: $"{blazorRootUrl}/authentication/login-callback",
            //        postLogoutRedirectUri: $"{blazorRootUrl}/authentication/logout-callback"
            //    );
            //}

            //#region Gateway Client
            //var configurationSection_Gateway = configurationSection?.GetChildren()?.Where(o => o.Key.Contains("_Gateway"))?.ToList();
            //if (configurationSection_Gateway?.Any() ?? false)
            //{
            //    foreach (var child in configurationSection_Gateway)
            //    {
            //        var gatewayClientId = child["ClientId"];
            //        if (!gatewayClientId.IsNullOrWhiteSpace())
            //        {
            //            var gatewayRootUrl = child["RootUrl"].TrimEnd('/');
            //            var gatewayClientSecret = child["ClientSecret"] ?? "1q2w3e*";
            //            await CreateApplicationAsync(
            //                name: gatewayClientId,
            //                type: OpenIddictConstants.ClientTypes.Confidential,
            //                consentType: OpenIddictConstants.ConsentTypes.Implicit,
            //                displayName: $"{gatewayClientId} Application",
            //                secret: gatewayClientSecret,
            //                grantTypes: new List<string>
            //                {
            //                    OpenIddictConstants.GrantTypes.AuthorizationCode
            //                },
            //                scopes: commonScopes,
            //                redirectUri: gatewayRootUrl + "/swagger/oauth2-redirect.html"
            //            );
            //        }
            //    }
            //}
            //#endregion
            #endregion
        }

        private async Task CreateApplicationAsync(
            [NotNull] string name,
            [NotNull] string type,
            [NotNull] string consentType,
            string displayName,
            string secret,
            List<string> grantTypes,
            List<string> scopes,
            string redirectUri = null,
            string postLogoutRedirectUri = null,
            List<string> permissions = null)
        {
            if (!string.IsNullOrEmpty(secret) && string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
            {
                throw new BusinessException(L["NoClientSecretCanBeSetForPublicApplications"]);
            }

            if (string.IsNullOrEmpty(secret) && string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase))
            {
                throw new BusinessException(L["TheClientSecretIsRequiredForConfidentialApplications"]);
            }

            //if (!string.IsNullOrWhiteSpace(name) && await _applicationManager.FindByClientIdAsync(name) != null)
            //{
            //    return;
            //    //throw new BusinessException(L["TheClientIdentifierIsAlreadyTakenByAnotherApplication"]);
            //}

            var client = await _applicationManager.FindByClientIdAsync(name);
            if (client == null)
            {
                var application = new OpenIddictApplicationDescriptor
                {
                    ClientId = name,
                    Type = type,
                    ClientSecret = secret,
                    ConsentType = consentType,
                    DisplayName = displayName
                };

                Check.NotNullOrEmpty(grantTypes, nameof(grantTypes));
                Check.NotNullOrEmpty(scopes, nameof(scopes));

                if (new[] { OpenIddictConstants.GrantTypes.AuthorizationCode, OpenIddictConstants.GrantTypes.Implicit }.All(grantTypes.Contains))
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeIdToken);

                    if (string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
                    {
                        application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeIdTokenToken);
                        application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeToken);
                    }
                }

                if (!redirectUri.IsNullOrWhiteSpace() || !postLogoutRedirectUri.IsNullOrWhiteSpace())
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Logout);
                }

                foreach (var grantType in grantTypes)
                {
                    if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode)
                    {
                        application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode);
                        application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.Code);
                    }

                    if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode || grantType == OpenIddictConstants.GrantTypes.Implicit)
                    {
                        application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Authorization);
                    }

                    if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode ||
                        grantType == OpenIddictConstants.GrantTypes.ClientCredentials ||
                        grantType == OpenIddictConstants.GrantTypes.Password ||
                        grantType == OpenIddictConstants.GrantTypes.RefreshToken ||
                        grantType == OpenIddictConstants.GrantTypes.DeviceCode)
                    {
                        application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Token);
                        application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Revocation);
                        application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Introspection);
                    }

                    if (grantType == OpenIddictConstants.GrantTypes.ClientCredentials)
                    {
                        application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.ClientCredentials);
                    }

                    if (grantType == OpenIddictConstants.GrantTypes.Implicit)
                    {
                        application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.Implicit);
                    }

                    if (grantType == OpenIddictConstants.GrantTypes.Password)
                    {
                        application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.Password);
                    }

                    if (grantType == OpenIddictConstants.GrantTypes.RefreshToken)
                    {
                        application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.RefreshToken);
                    }

                    if (grantType == OpenIddictConstants.GrantTypes.DeviceCode)
                    {
                        application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.DeviceCode);
                        application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Device);
                    }

                    if (grantType == OpenIddictConstants.GrantTypes.Implicit)
                    {
                        application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.IdToken);
                        if (string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
                        {
                            application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.IdTokenToken);
                            application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.Token);
                        }
                    }
                }

                var buildInScopes = new[]
                {
                OpenIddictConstants.Permissions.Scopes.Address,
                OpenIddictConstants.Permissions.Scopes.Email,
                OpenIddictConstants.Permissions.Scopes.Phone,
                OpenIddictConstants.Permissions.Scopes.Profile,
                OpenIddictConstants.Permissions.Scopes.Roles
            };

                foreach (var scope in scopes)
                {
                    if (buildInScopes.Contains(scope))
                    {
                        application.Permissions.Add(scope);
                    }
                    else
                    {
                        application.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.Scope + scope);
                    }
                }

                if (redirectUri != null)
                {
                    if (!redirectUri.IsNullOrEmpty())
                    {
                        if (!Uri.TryCreate(redirectUri, UriKind.Absolute, out var uri) || !uri.IsWellFormedOriginalString())
                        {
                            throw new BusinessException(L["InvalidRedirectUri", redirectUri]);
                        }

                        if (application.RedirectUris.All(x => x != uri))
                        {
                            application.RedirectUris.Add(uri);
                        }
                    }
                }

                if (postLogoutRedirectUri != null)
                {
                    if (!postLogoutRedirectUri.IsNullOrEmpty())
                    {
                        if (!Uri.TryCreate(postLogoutRedirectUri, UriKind.Absolute, out var uri) || !uri.IsWellFormedOriginalString())
                        {
                            throw new BusinessException(L["InvalidPostLogoutRedirectUri", postLogoutRedirectUri]);
                        }

                        if (application.PostLogoutRedirectUris.All(x => x != uri))
                        {
                            application.PostLogoutRedirectUris.Add(uri);
                        }
                    }
                }

                if (permissions != null)
                {
                    await _permissionDataSeeder.SeedAsync(
                        ClientPermissionValueProvider.ProviderName,
                        name,
                        permissions,
                        null
                    );
                }

                await _applicationManager.CreateAsync(application);
            }
        }
    }
    public interface IOpenIddictDataSeeder
    {
        Task SeedAsync();
    }
    public class OpenIddictDataSeedContributor : IDataSeedContributor, ITransientDependency
    {
        protected IOpenIddictDataSeeder IOpenIddictDataSeeder { get; }
        public OpenIddictDataSeedContributor(IOpenIddictDataSeeder iOpenIddictDataSeeder)
        {
            IOpenIddictDataSeeder = iOpenIddictDataSeeder;
        }
        public virtual Task SeedAsync(DataSeedContext context)
        {
            return IOpenIddictDataSeeder.SeedAsync();
        }
    }
}
